#include <iostream>
#include <cstring>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

// 1.信号概念:
/*
    信号本质上是一种通信机制,用户或者操作系统通过以给指定进程发送信号来通知进程,某件事情已经发生,等待进程后续进行处理.
    所以进程需要知道对应的信号应该如何被处理,进程可以存储信号,以便如果无法及时处理的话对该信号做存储再延后处理.
*/

// 2.信号产生: 软件/硬件/组合键产生

sighandler_t osigpipe_handler=nullptr;
void sigpipe_handler(int signum) {

    std::cout<<"signum: "<<signum<<std::endl;

    osigpipe_handler(signum);
}

void test_sigpipe() { 

    int pipefd[2];

    pipe(pipefd);

    // 子进程不断写
    if(0==fork()) {
        close(pipefd[0]); // 关闭读端
        
        osigpipe_handler=signal(SIGPIPE,sigpipe_handler); // 捕获SIGPIPE信号

        while(true) {

            const char* buffer="message...";

            write(pipefd[1],buffer,sizeof(buffer)-1);

        }
    }

    std::cout<<"parent close pipefd[1]"<<std::endl;
    close(pipefd[1]); // 关闭写端

    for(int i=0;i<5;++i) {

        char buffer[1024];
        
        ssize_t n=read(pipefd[0],buffer,sizeof(buffer)-1);
        buffer[n]='0';

        std::cout<<"parent "<<buffer<<std::endl;

        sleep(1);
    }

    std::cout<<"parent close pipefd[0]"<<std::endl;
    close(pipefd[0]); // 关闭读端
}

sighandler_t osigchld_handler=nullptr;
void sigchld_handler(int signum) {

    printf("parent# signum: %d pid: %d\n",signum,getpid());

    // 当收到SIGCHLD信号时,调用waitpid回收子进程
    // pid_t waitpid(pid_t pid, int *status, int options);
    // 回收子进程成功返回子进程的pid,回收子进程过程中出错返回-1,如果使用非阻塞等待(WNOHANG)式的调用子进程还未结束返回0

    pid_t id=0;

    while((id=waitpid(-1,nullptr,WNOHANG))>0) {
        printf("parent# id: %d\n",id);
    }

}

void test_sigchld() {

    if(0==fork()) {

        sleep(5);

        std::cout<<"child# exit pid: "<<getpid()<<std::endl;
        exit(0);
    }

    // 解决僵尸进程的方法:
    // 1.wait/waitpid回收子进程
    // osigchld_handler=signal(SIGCHLD,sigchld_handler);
    // 2.手动忽略SIGCHLD信号
    signal(SIGCHLD,SIG_IGN);

    while(true) {

    }
}

void hardware_handler(int signum) {
    std::cout<<signum<<std::endl;
}

void test_hardware() {

    pid_t id=fork();

    if(0==id) {

        signal(SIGFPE,hardware_handler); 
        //int x=3/0; // 不断产生SIGFPE

        // 1.除零错误
        //int x=3/0; // 8) SIGFPE
        // 2.mod零
        //int x=3%0; // 8) SIGFPE
        // 3.对空指针引用
        //int *p=nullptr; // 11) SIGSEGV
        //*p=3;
        // 4.野指针 // 11) SIGSEGV
        // int *p=new int;
        // delete p;
        // *p=3;
    }

    int stat=0;

    waitpid(id,&stat,0);
    
    printf("stat: %d exitcode: %d signal: %d\n",stat,(stat>>8)&0xff,stat&0x7f);
}

// 3.信号发送

// 4.信号保存
// 实际执行信号的处理动作 --- 递达(Delievery) --- handler函数指针数组
// 信号从产生到递达之间的状态(发送中/待处理) --- 未决(Pending) --- pending位图
// 进程可与选择阻塞某个信号 --- 阻塞(Block) --- block位图

// 5.信号处理
// 进程从内核态转变为用户态的前一刻信号被处理

// 6.信号操作
// 1.signal捕获
void signal_handler(int signum) {

    for(int i=15;i>=0;--i) {
        printf("signum: %d 处理中 %ds\n",signum,i);
        sleep(1);
    }

}

void test_signal() {

    signal(SIGINT,signal_handler);
    // 1.如果某信号是通过signal捕获的则该信号对应的信号屏蔽字集都为空
    // 2.如果某信号正在被处理,进程再次收到该相同的信号
    //      则该信号会被阻塞,直到该信号被处理完而且多次收到一样的信号则该信号最多被触发两次
    //      当一个信号第一次被递达, OS在处理信号时, 在处理期间会将该信号屏蔽, 当处理完之后解除该信号的屏蔽, 
    //      也就是说上述问题, 同一时刻发送两次, OS也只能一个一个的进行对于信号的处理
    //      为了保证系统安全, 避免信号递归式的处理, OS在同一时刻下同一种信号只能操作1次, 就是通过block屏蔽字实现的

}

// 2.int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)捕获
/*
    struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t   sa_mask;
        int        sa_flags;
        void     (*sa_restorer)(void);
    };
*/

void sigaction_handler(int signum) {

    for(int i=15;i>=0;--i) {

        sigset_t pend;
        sigpending(&pend);

        for(int i=1;i<31;i++) {
            if(sigismember(&pend,i)) {
                std::cout<<1;
            } else {
                std::cout<<0;
            }
        }
        std::cout<<std::endl;

        sleep(1);
    }
}

// 阻塞vs屏蔽
void test_sigaction() {

    printf("pid %d\n",getpid());

    struct sigaction act,oact;
    memset(&act,0,sizeof(act));

    act.sa_handler=sigaction_handler; // 注册自定义处理

    sigset_t set,oset;
    sigemptyset(&set);
    
    sigaddset(&set,SIGQUIT); // 屏蔽SIGQUIT
    act.sa_mask=set;

    sigaction(SIGINT,&act,&oact); // 捕获SIGINT

    while(true) {

    }
}

void print_sigset(sigset_t &set) {

    for(int i=1;i<32;++i) {

        if(sigismember(&set,i)) {
            std::cout<<1;
        } else {
            std::cout<<0;
        }

    }
    std::cout<<std::endl;
}

// 将对应的信号阻塞
void signoblock(int signo) {

    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,signo);
    
    sigprocmask(SIG_BLOCK,&set,nullptr);
}

// 设置添加需要屏蔽对应的信号
void signomask(sigset_t &set,int signo) {
    sigaddset(&set,signo);
}

// 通过signal捕获
void handler1(int signum) {

    std::cout<<signum<<std::endl;

    //1.是先将未决位图该信号对应的位置置空再去递达,还是先递达再去置空?---先置空再递达
    sigset_t pendset;

    sigpending(&pendset);
    std::cout<<"pendset: ";
    print_sigset(pendset);

    // 2.如果某信号正在被处理,则该信号在处理过程中会被阻塞---防止递归递达
    sigset_t oblockset;

    sigprocmask(SIG_BLOCK,nullptr,&oblockset);
    std::cout<<"oblockset: ";
    print_sigset(oblockset);
    // 发送SIGINT信号的现象:
    //pendset: 0000000000000000000000000000000
    //oblockset: 0100000000000000000000000000000

}

// 阻塞vs屏蔽
// 如果某一个信号通过sigaction设置了信号屏蔽子集
// 表示如果该信号正在处理,则自动阻塞被屏蔽的信号,处理完成之后解除阻塞
void handler2(int signum) {

    sigset_t oblockset;

    // 信号处理中打印阻塞位图
    sigprocmask(SIG_BLOCK,nullptr,&oblockset);
    std::cout<<"oblockset: ";
    print_sigset(oblockset);
}

void test_pending() {
    
    // 先阻塞一个信号
    signoblock(SIGQUIT);

    //signal(SIGINT,handler1);
    struct sigaction act,oact;
    memset(&act,0,sizeof(act));
    act.sa_handler=handler2;
    
    sigset_t maskset;
    sigemptyset(&maskset);
    // 设置所以信号屏蔽
    for(int i=1;i<32;++i) {
        signomask(maskset,i);
    }

    act.sa_mask=maskset;

    // 捕获SIGINT信号
    sigaction(SIGINT,&act,&oact);

    std::size_t timeremain=sleep(10);

    std::cout<<"timeremain: "<<timeremain<<std::endl;

    // 信号未处理的时候打印
    sigset_t oblockset;

    sigprocmask(SIG_BLOCK,nullptr,&oblockset);
    std::cout<<"after oblockset: ";
    print_sigset(oblockset);

    //oblockset: 1111111101111111110111111111111
    //timeremain: 3
    //after oblockset: 0010000000000000000000000000000

    while(true) {

    }

}

void handler3(int signum) {

    std::cout<<signum<<std::endl;

}

void test_block() {

    // 试着将所有普通信号阻塞
    // for(int i=1;i<32;++i) {
    //     signoblock(i);
    // }

    // 将所有普通信号捕获
    for(int i=1;i<32;++i) {
        signal(i,handler3);
    }
    // 特殊信号 9) SIGKILL 19) SIGSTOP 不能被捕获和阻塞

    while(true) {
        sigset_t set;
        sigpending(&set);
        print_sigset(set);
        sleep(1);
    }

}

void testsigapi() {

    //1. void abort(void);
    // if(fork()==0) {

    //     abort(); // 6) SIGABRT // 给当前进程发送SIGABRT信号

    // }

    // int stat=0;
    // waitpid(-1,&stat,0);
    // printf("stat: %d exitcode: %d signal: %d\n",stat,(stat>>8)&0xff,stat&0x7f);

    // //2. int raise(int sig);
    // if(fork()==0) {

    //     raise(SIGKILL); // 给当前进程发送指定信号

    // }

    // int stat=0;
    // waitpid(-1,&stat,0);
    // printf("stat: %d exitcode: %d signal: %d\n",stat,(stat>>8)&0xff,stat&0x7f);

    //3. int kill(pid_t pid, int sig); // 给指定进程发送指定信号
    // pid_t id=fork();
    // if(id==0)
    // {
    //     while(true) {

    //     }
    // }

    // kill(id,SIGQUIT);

    // int stat=0;
    // waitpid(-1,&stat,0);
    // printf("stat: %d exitcode: %d signal: %d\n",stat,(stat>>8)&0xff,stat&0x7f);
    
    // 4.unsigned int alarm(unsigned int seconds); //定时闹钟

    pid_t id=fork();

    if(id==0) {

        alarm(3); //14) SIGALRM // 默认是退出进程
        while(true) {

        }
    }

    int stat=0;
    waitpid(id,&stat,0);
    printf("stat: %d exitcode: %d signal: %d\n",stat,(stat>>8)&0xff,stat&0x7f);
}

int main() {

    std::cout<<"WNOHANG: "<<WNOHANG<<std::endl;

    //test_sigpipe();
    //test_sigchld();
    //test_hardware();
    //test_signal();
    //test_sigaction();
    //test_pending();
    //test_block();
    testsigapi();

    while(true) {

    }

    return 0;
}